package cn.miketsu.iced.reflect;

import cn.miketsu.iced.annotation.*;
import cn.miketsu.iced.annotation.mysql.AutoIncrement;

import java.lang.reflect.Field;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class MysqlCrateTable {

    private static final Class<NotNull> NotNullClass = NotNull.class;

    private static final Class<DataType> DataTypeClass = DataType.class;

    private static final Class<Comment> CommentClass = Comment.class;

    private static final Class<PrimaryKey> PrimaryKeyClass = PrimaryKey.class;

    private static final Class<AutoIncrement> AutoIncrementClass = AutoIncrement.class;

    private static final Class<Ignore> IgnoreClass = Ignore.class;

    private static final Map<Class<?>, String> dataTypeMap = new HashMap<>(17);

    static {
        dataTypeMap.put(byte.class, " int ");
        dataTypeMap.put(Byte.class, " int ");
        dataTypeMap.put(int.class, " int ");
        dataTypeMap.put(Integer.class, " int ");
        dataTypeMap.put(long.class, " bigint ");
        dataTypeMap.put(Long.class, " bigint ");
        dataTypeMap.put(double.class, " double ");
        dataTypeMap.put(Double.class, " double ");
        dataTypeMap.put(float.class, " float ");
        dataTypeMap.put(Float.class, " float ");
        dataTypeMap.put(char.class, " char(1) ");
        dataTypeMap.put(Character.class, " char(1) ");
        dataTypeMap.put(boolean.class, " char(2) ");
        dataTypeMap.put(Boolean.class, " char(2) ");
        dataTypeMap.put(short.class, " int ");
        dataTypeMap.put(Short.class, " int ");
        dataTypeMap.put(Date.class, " timestamp ");
    }

    /**
     * 获取建表语句
     *
     * @param beans model对象
     * @return 建表语句List
     * @author wangzefeng
     * @since 1.0-SNAPSHOT
     */
    public static List<String> create(List<Object> beans) {
        ArrayList<String> ddls = new ArrayList<>(beans.size());
        beans.forEach(bean -> {
            Field[] fields = bean.getClass().getDeclaredFields();
            Field.setAccessible(fields, true);
            StringBuilder ddl = new StringBuilder("CREATE TABLE IF NOT EXISTS ");
            ddl.append("`");
            ddl.append(camlToLine(bean.getClass().getSimpleName()));
            ddl.append("`");
            ddl.append("( ");
            for (Field field : fields) {
                if (field.getAnnotation(IgnoreClass) == null) {
                    ddl.append("`");
                    ddl.append(camlToLine(field.getName()));
                    ddl.append("` ");
                    fieldDetail(field, ddl);
                    ddl.append(" ,");
                }
            }
            StringBuilder pk = new StringBuilder("");
            boolean ife = false;
            pk.append("PRIMARY KEY(");
            for (Field field : fields) {
                if (field.getAnnotation(IgnoreClass) == null) {
                    if (field.getAnnotation(PrimaryKeyClass) != null) {
                        pk.append("`");
                        pk.append(camlToLine(field.getName()));
                        pk.append("`");
                        pk.append(",");
                        ife = true;
                    }
                }
            }
            if (ife) {
                ddl.append(pk.substring(0, pk.length() - 1));
                ddl.append(")");
            }
            ddl.append(" )ENGINE=INNODB DEFAULT CHARSET=utf8 ");
            addTableComment(bean.getClass(), ddl);
            ddls.add(String.valueOf(ddl));
        });
        return ddls;
    }

    /**
     * 获取建表语句
     *
     * @param packageName model相关类所在包的包名
     * @return 建表语句List
     * @author wangzefeng
     * @since 1.0-SNAPSHOT
     */
    public static List<String> create(String packageName) throws Exception {
        return create(CreateBean.getObjectsInPackage(packageName));
    }

    /**
     * 添加数据类型、非空约束、自增和注释信息
     *
     * @param column 字段
     * @param ddl    建表语句
     * @author wangzefeng
     * @since 1.1-SNAPSHOT
     */
    private static void fieldDetail(Field column, StringBuilder ddl) {
        ddl.append(" ");
        if (column.getAnnotation(DataTypeClass) != null) {
            ddl.append(column.getAnnotation(DataTypeClass).value().getSql());
            if (column.getAnnotation(DataTypeClass).accuracy()>0){
                ddl.append("(");
                ddl.append(column.getAnnotation(DataTypeClass).accuracy());
                ddl.append(") ");
            }
        } else {
            String dt = dataTypeMap.get(column.getType());
            ddl.append(dt == null ? " varchar(200) " : dt);
        }
        if (column.getAnnotation(NotNullClass) != null) {
            ddl.append(" not null ");
        }
        if (column.getAnnotation(AutoIncrementClass) != null) {
            ddl.append(" AUTO_INCREMENT ");
        }
        if (column.getAnnotation(CommentClass) != null) {
            ddl.append(" comment '");
            ddl.append(column.getAnnotation(CommentClass).value());
            ddl.append("'");
        }
    }

    /**
     * 添加表注释
     *
     * @param table 表
     * @param ddl   建表语句
     * @author wangzefeng
     * @since 1.0-SNAPSHOT
     */
    private static void addTableComment(Class<?> table, StringBuilder ddl) {
        if (table.getAnnotation(CommentClass) != null) {
            ddl.append(" COMMENT= '");
            ddl.append(table.getAnnotation(CommentClass).value());
            ddl.append("';");
        }
    }

    /**
     * 字符串驼峰命名转下划线命名
     *
     * @param str 驼峰命名字符串
     * @return 下划线命名字符串
     * @since 1.0-SNAPSHOT
     */
    private static String camlToLine(String str) {
        if (!Character.isLowerCase(str.charAt(0)))
            str = (new StringBuilder()).append(Character.toLowerCase(str.charAt(0))).append(str.substring(1)).toString();
        Pattern humpPattern = Pattern.compile("[A-Z]");
        Matcher matcher = humpPattern.matcher(str);
        StringBuilder sb = new StringBuilder();
        while (matcher.find()) {
            matcher.appendReplacement(sb, "_" + matcher.group(0).toLowerCase());
        }
        matcher.appendTail(sb);
        return sb.toString();
    }
}
